2023-01-05
Setting up pipewire for an external DAC
A few weeks ago, I decided to treat myself to some more desk clutter: An external DAC and amplifier box! Until now I used the lackluster on-board audio on my workstation, so this is a welcome improvement.

Now unfortunately pipewire by default does not automatically adjust its sampling rate based on the rate of the audio source and the audio sink, however it can be easiely configured to do so.
First, you want to get the ID if the alsa card device.
$ cat /proc/asound/cards
0 [HDMI ]: HDA-Intel - HDA ATI HDMI
HDA ATI HDMI at 0xfeb64000 irq 44
1 [Generic ]: HDA-Intel - HD-Audio Generic
HD-Audio Generic at 0xfeb60000 irq 16
2 [Camera ]: USB-Audio - Web Camera
Web Camera Web Camera at usb-0000:00:16.2-1, high speed
3 [K38 ]: USB-Audio - K38
KTMicro K38 at usb-0000:00:16.0-2, full speed
4 [Pro ]: USB-Audio - FiiO K5 Pro
GuangZhou FiiO Electronics Co.,Ltd FiiO K5 Pro at usb-0000:00:13.2-1, high spee
Check the descriptions of each device to find your DAC. In the output above I highlighted mine. The number at the left is the ID. Now we need to find out which rates it supports and which formats.
$ cat /proc/asound/card4/stream0
GuangZhou FiiO Electronics Co.,Ltd FiiO K5 Pro at usb-0000:00:13.2-1, high spee : USB Audio
Playback:
Status: Running
Interface = 1
Altset = 1
Packet Size = 144
Momentary freq = 95988 Hz (0xb.ffa0)
Feedback Format = 16.16
Interface 1
Altset 1
Format: S32_LE
Channels: 2
Endpoint: 0x01 (1 OUT) (ASYNC)
Rates: 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, 705600, 768000
Data packet interval: 125 us
Bits: 32
Channel map: FL FR
Sync Endpoint: 0x81 (1 IN)
Sync EP Interface: 1
Sync EP Altset: 1
Implicit Feedback Mode: No
Interface 1
Altset 2
Format: S16_LE
Channels: 2
Endpoint: 0x01 (1 OUT) (ASYNC)
Rates: 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, 705600, 768000
Data packet interval: 125 us
Bits: 16
Channel map: FL FR
Sync Endpoint: 0x81 (1 IN)
Sync EP Interface: 1
Sync EP Altset: 2
Implicit Feedback Mode: No
Interface 1
Altset 3
Format: SPECIAL DSD_U32_BE
Channels: 2
Endpoint: 0x01 (1 OUT) (ASYNC)
Rates: 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, 705600, 768000
Data packet interval: 125 us
Bits: 32
DSD raw: DOP=0, bitrev=0
Channel map: FL FR
Sync Endpoint: 0x81 (1 IN)
Sync EP Interface: 1
Sync EP Altset: 3
Implicit Feedback Mode: No
Again, I highlighted the relevant lines in the output. We can neatly see all formats the DAC understands and the rates it supports them at. Now we want to create a pipewire configuration file. Pipewire is a user service, not a system service, so the sane thing to do here is to create a config file in your home directory.
$ mkdir -p ~/.config/pipewire/pipewire.conf.d/
$ $EDITOR ~/.config/pipewire/pipewire.conf.d/80-sample-rate.conf
Luckily pipewire already sets a good format by default for me, so I'll only be configuring the rate.
context.properties = {
default.clock.allowed-rates = [ 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000, 705600, 768000 ]
}
Pipewire allows you to set up to 16 rates it can switch between. I set these to all the rates my DAC supports. It's usually a good idea to always leave the default 44100
in there as well, so that there won't be any issues if the DAC is not connected and pipewire falls back to some other audio sink. As an example, my DAC can be turned off, in which case pipwire will output audio via HDMI to my monitor which then plays it over its speakers. As you can imagine, that audio sink does not support a very high rate. I switch between DAC with headphones and monitor speakers regularly, so both being supported first class is a must for me.
Now restart pipewire (and your session manager, which likely is wireplumber!) and it just automatically adjust the rate. You can use the pw-top
TUI tool to view the current rate of sources and sinks.

Do not be alarmed if the displayed rate does not match the highest one you configured! Pipewire automatically choses the one that fits best to the audio source and the audio sink. Any sane music player will adjust its rate according to the bitrate of the currently playing song. Most of your recordings will not be high-quality enough to really push the limits of your DAC and pipewire does the smart thing here by not setting the rate to a needlessly high value. As such it's a good idea to put all rates your DAC supports into the array in the config file, even the ones you think are low.
My Dac has a LED shining behind the volume controls. Its blue for rates matching CD quality and below, yellow for everything above. It's kinda fun seeing it change based on what songs I play. A lot of my music library is ripped from discs, so unsurprisingly it stays blue a lot.
What's annoying is that when you test your setup and you hear small pops and cracklings, it's not immediately obvious whether that's from the configuration being bad or is part of the audio file. When I set this up initially, I tested my setup with In My Afterlife by Emma Ruth Rundle (Playing the flac file of course, not YouTube. That link is just here for illustration purposes.) and a little bump in the actual recording at 12 seconds in got me a bit annoyed, because I at first confused with pipewire acting up. Also 16 seconds in there is some background noise and in one part the microphone is mildy overloaded. The things you notice with better equipment is ... quite something. The rest of the songs on that brilliant album have a better recording quality luckily.
Oh, and another thing! If you use software that allows you to set the profile of the audio device, like pavucontrol, then just don't. Leave it on the Analag Stereo one. Yes, the IEC958 one looks more sophisticated, but you really don't want to use that one. Same with the Pro Audio one, unless you actually, really know what you are doing.

And finally, you probably want a way to adjust the volume of the DAC sink as well; Yes, that is unrelated to the hardware volume you control by turning the knob. At least I found mine to be considerably to loud, even at lowest gain, after less then a quarter turn of the volume know. Pipewire defaults to a a sink volume of 1.0
; I set mine to 0.6
. The most convenient way to adjust sink levels is via wireplumber, I find.
$ wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
$ wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-